package com.clp.inflxudb.core.domain;

import com.clp.inflxudb.core.enums.CompareOp;
import com.clp.inflxudb.core.enums.FluxKVType;
import com.clp.inflxudb.core.enums.FluxKey;
import com.influxdb.annotations.Column;
import com.influxdb.annotations.Measurement;
import lombok.Data;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * InfluxDB中：一个 measurement 的一行中，所有的tag字段值均存储为字符串，即使其在POJO类中不是以String定义的
 */
public abstract class FluxPoint {
    @Data
    private static class FluxPointField {
        private String timestampName;
        private List<String> tagNames;
        private List<String> fieldNames;

        public FluxPointField() {
            this.timestampName = null;
            this.tagNames = new ArrayList<>();
            this.fieldNames = new ArrayList<>();
        }
    }

    private static Map<Class, FluxPointField> map = new HashMap<>();

    private static <T extends FluxPoint> FluxPointField getFluxPointField(Class<T> clazz) {
        FluxPointField fluxPointField = map.get(clazz);
        if (fluxPointField != null) return fluxPointField;

        synchronized (map) {
            fluxPointField = map.get(clazz);
            if (fluxPointField != null) return fluxPointField;

            fluxPointField = new FluxPointField();
            for (Field field : clazz.getDeclaredFields()) {
                field.setAccessible(true);
                if (!field.isAnnotationPresent(Column.class)) {
                    continue;
                }
                Column column = field.getAnnotation(Column.class);
                if (column.timestamp()) {
                    fluxPointField.setTimestampName(field.getName());
                } else if (column.tag()) {
                    fluxPointField.getTagNames().add(field.getName());
                } else {
                    fluxPointField.getFieldNames().add(field.getName());
                }
            }
            if (fluxPointField.getTimestampName() == null) {
                throw new RuntimeException("未定义时间戳字段！");
            }
            map.put(clazz, fluxPointField);
        }
        return fluxPointField;
    }

    public static <T extends FluxPoint> String getMeasurementName(Class<T> clazz) {
        if (!clazz.isAnnotationPresent(Measurement.class)) {
            return "";
        }
        return clazz.getAnnotation(Measurement.class).name();
    }

    public static <T extends FluxPoint> List<String> getTagNames(Class<T> clazz) {
        return getFluxPointField(clazz).getTagNames();
    }

    public static <T extends FluxPoint> List<String> getFieldNames(Class<T> clazz) {
        return getFluxPointField(clazz).getFieldNames();
    }

    public static <T extends FluxPoint> String getTimestampName(Class<T> clazz) {
        return getFluxPointField(clazz).getTimestampName();
    }

    /**
     * 创建子类的一个实例
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T extends FluxPoint> T getChildInstance(Class<T> clazz) {
        T returnObj = null;
        try {
            Constructor<T> constructor = clazz.getConstructor();
            returnObj = constructor.newInstance();
        } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
            return returnObj;
        }
        return returnObj;
    }

    /**
     * 获取measurement名称
     *
     * @return
     */
    public String getMeasurementName() {
        return getMeasurementName(this.getClass());
    }

    public List<String> getTagNames() {
        return getTagNames(this.getClass());
    }

    public List<String> getFieldNames() {
        return getFieldNames(this.getClass());
    }

    public String getTimestampName() {
        return getTimestampName(this.getClass());
    }

    /**
     * 根据属性名获取值
     */
    public <T> T get(String fieldName, Class<T> clazz) {
        Object obj = null;
        try {
            Field field = this.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            obj = field.get(this);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            return (T) obj;
        }
        return (T) obj;
    }

    /**
     * 设置属性名对应的值，设置失败返回false
     *
     * @param fieldName
     * @param obj
     * @return
     */
    public boolean set(String fieldName, Object obj) {
        if (obj == null) return false;
        Field field = null;
        try {
            field = this.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            Class<?> fieldClass = field.getType();
            if (fieldClass == obj.getClass()) {
                field.set(this, obj);
                return true;
            }
            if (fieldClass == Instant.class) {
                field.set(this, SimpleDateUtil.stringToInstant(obj.toString()));
                return true;
            }
            if (fieldClass == Byte.class) {
                field.set(this, Byte.valueOf(obj.toString()));
                return true;
            }
            if (fieldClass == Short.class) {
                field.set(this, Short.valueOf(obj.toString()));
                return true;
            }
            if (fieldClass == Integer.class) {
                field.set(this, Integer.valueOf(obj.toString()));
                return true;
            }
            if (fieldClass == Long.class) {
                field.set(this, Long.valueOf(obj.toString()));
                return true;
            }
            if (fieldClass == Float.class) {
                field.set(this, Float.valueOf(obj.toString()));
                return true;
            }
            if (fieldClass == Double.class) {
                field.set(this, Double.valueOf(obj.toString()));
                return true;
            }
            if (fieldClass == String.class) {
                field.set(this, obj.toString());
                return true;
            }
            return false;
        } catch (NoSuchFieldException | IllegalAccessException e) {
            return false;
        }
    }

    public FluxKVGroup toFluxKVGroup() {
        List<String> tagNames = getTagNames();
        List<String> fieldNames = getFieldNames();
        String timestampName = getTimestampName();
        FluxKVGroup kvgTags = null;
        FluxKVGroup kvgFields = null;
        FluxKVGroup kvgTimestamp = null;

        Object timestampObj = get(timestampName, Object.class);
        if (timestampObj != null) {
            kvgTimestamp = new FluxKVGroup(FluxKey._time, timestampObj, CompareOp.EQUAL, FluxKVType.TIMESTAMP);
        }

        if (!fieldNames.isEmpty()) {
            int index = 0;
            Object valObj = get(fieldNames.get(index), Object.class);
            while(valObj == null) {
                index += 1;
                valObj = get(fieldNames.get(index), Object.class);
            }
            kvgFields = new FluxKVGroup(fieldNames.get(index), valObj, CompareOp.EQUAL, FluxKVType.FIELD);
            index += 1;
            while(index < fieldNames.size()) {
                String fieldName = fieldNames.get(index);
                valObj = get(fieldName, Object.class);
                if (valObj != null) {
                    kvgFields = kvgFields.connAnd(fieldName, valObj, CompareOp.EQUAL, FluxKVType.FIELD);
                }
                index += 1;
            }
        }

        if (!tagNames.isEmpty()) {
            int index = 0;
            Object valObj = get(tagNames.get(index), Object.class);
            while(valObj == null) {
                index += 1;
                valObj = get(tagNames.get(index), Object.class);
            }
            kvgTags = new FluxKVGroup(tagNames.get(index), valObj.toString(), CompareOp.EQUAL, FluxKVType.TAG);
            index += 1;
            while(index < tagNames.size()) {
                String tagName = tagNames.get(index);
                valObj = get(tagName, Object.class);
                if (valObj != null) {
                    kvgTags = kvgTags.connAnd(tagName, valObj.toString(), CompareOp.EQUAL, FluxKVType.TAG);
                }
                index += 1;
            }
        }

        FluxKVGroup kvgFinal;
        if (kvgTimestamp != null) {
            kvgFinal = kvgTimestamp.connAnd(kvgTags).connAnd(kvgFields);
        } else if (kvgTags != null) {
            kvgFinal = kvgTags.connAnd(kvgFields);
        } else {
            kvgFinal = kvgFields;
        }

        return kvgFinal;
    }
}
